
/*
 * @项目名称: 模板开发
 * @FilePath: /meek/src/geometry/polygon.js
 * @Author: 
 * @Date: 2017-03-23T11:12:12+08:00
 * @LastEditors: yangxianghong@datatang.com
 * @LastEditTime: 2023-07-13 16:19:24
 * Copyright (c) 2018-2022
 */

/**
 * Created by zypc on 2016/11/13.
 */

import Geometry from './geometry'
import Extent from './extent'

import {linearRings} from './support/interpolate'
import {linearRingsAreOriented,orientLinearRings} from './support/orient'
import { squaredSegmentDistance, distance, pointDistanceToSegment, getProjectionPointOnLine } from './support/geometryutil'
import { ExtentUtil } from '../geometry/support/extentutil'

/**
 * 面类和数据结构
 *
 * 多边形是<b>闭合数据结构</b>，所有首尾点必须相同。
 *
 * 多边形顶点存储是一个二维数组，数组中第一条闭合点串表示多边形
 * 的最外层，第二条及其以后表示多边形的内部洞环。
 *
 * @class Polygon
 * @extends Geometry
 * @module geometry
 * @constructor
 * @example
 *
 *  var polygon = new Datatang.Polygon([[100, 100], [120, 130],[50, 50],[100, 100]])
 *
 */
export default class Polygon extends Geometry {

  /**
   * 创建一个Polygon实例
   * @constructor
   * @param rings
   */
  constructor (rings = []) {
    super()

    this._rings = []

    /**
     * 记录当前多边形内点更新的次数
     * @type {number}
     * @private
     */
    this._flatInteriorPointRevision = -1

    /**
     * 缓存当前多边形内点
     * @type {null}
     * @private
     */
    this._flatInteriorPoint = null

    /**
      * 固定的顶点数量
     */
    this.fixedPointSize = null

    /**
     * 更新多边形的坐标
     */
    this.bezier = []
    this.setCoordinates(rings)
  }

  /**
   * 获取图形的最小外接矩形(MBR-Minimum Bounding Rectangle)
   * 可用来对图形进行拖动、旋转、缩放
   *
   * @method extent
   * @abstrct function
   * @returns {Extent}
   */
  get extent () {
    if (this._extent === null) {
      const coords = this.getCoordinates();

      // 计算最小外界矩形，只考虑外围环
      if (coords.length > 0) {
        let rings = coords[0].concat(this._getAllControls())

        let xmin = Number.POSITIVE_INFINITY
        let ymin = Number.POSITIVE_INFINITY
        let xmax = Number.NEGATIVE_INFINITY
        let ymax = Number.NEGATIVE_INFINITY

        for (let ring of rings) {
          xmin = Math.min(xmin, ring[0])
          ymin = Math.min(ymin, ring[1])
          xmax = Math.max(xmax, ring[0])
          ymax = Math.max(ymax, ring[1])
        }

        this._extent = new Extent(xmin, ymin, xmax, ymax)
      }
    }

    return this._extent
  }

  /**
   * 获取对象的几何类型
   *
   * @method geometryType
   * @abstrct function
   * @returns {String}
   */
  get geometryType () { return Geometry.POLYGON }

  /**
   * 设置多边形的边，如果设置了边，则需要重新计算
   * 外接矩形
   *
   * @property rings
   */
  get rings () { return this._rings }
  set rings (value) {
    this._rings = value
    this._extent = null
  }

  changed() {
    super.changed();
    this._extent = null;
  }

  getArea() {
    const coords = this.getCoordinates()[0];

    const start = coords[0]
    const end = coords[coords.length - 2]

    let sum = 0
    for (let i = 0, len = coords.length - 2; i < len; i++) {
      const coord = coords[i];
      const next = coords[i + 1];
      sum += coord[0] * next[1] - next[0] * coord[1]
    }
    let area = Math.abs(sum - (start[0] * end[1] - start[1] * end[0])) / 2
    return area;
  }

  /**
   * 多边形添加线段
   *
   * @method addRing
   * @param ring {Array}
   *
   */
  addRing (ring) {
    this.rings.push(ring)
    this._extent = null
  }

  /**
   * 判断点是否在多边形上
   *
   * @method containsXY
   * @param x {Number}
   * @param y {Number}
   * @returns {Boolean}
   */
  containsXY (x, y) {
    const coords = this.getCoordinates()
    const outRing = coords[0]

    let contains = false
    if (this._inOneRing(x, y, outRing)) {
      contains = true
    }

    if (coords.length > 1 && contains) {
      const coordsTemp = coords.slice(1)

      const inHole = coordsTemp.some( ring =>
        this._inOneRing(x, y, ring)
      )

      if (inHole) {
        contains = false
      }
    }

    return contains
  }

  /**
   * 判断 x 和 y 的值是否在环内
   *
   * @param x {Number}
   * @param y {Number}
   * @param ring {Array}
   * @returns {Boolean}
   * @private
   */
  _inOneRing (x, y, ring) {
    const px = x
    const py = y
    let flag = false

    for (let i = 0, l = ring.length, j = l - 1; i < l; j = i, i++) {
      const sx = ring[i][0]
      const sy = ring[i][1]
      const tx = ring[j][0]
      const ty = ring[j][1]

      // 点与多边形顶点重合
      if ((sx === px && sy === py) || (tx === px && ty === py)) {
        return true
      }

      // 判断线段两端点是否在射线两侧
      if ((sy < py && ty >= py) || (sy >= py && ty < py)) {
        // 线段上与射线 Y 坐标相同的点的 X 坐标
        let x = sx + (py - sy) * (tx - sx) / (ty - sy)

        // 点在多边形的边上
        if (x === px) {
          return true
        }

        // 射线穿过多边形的边界
        if (x > px) {
          flag = !flag
        }
      }
    }

    // 射线穿过多边形边界的次数为奇数时点在多边形内
    return flag
  }

  /**
   * 计算多边形的内点
   *
   * @method getFlatInteriorPoint
   * @returns {Array} 返回内点，格式 [x, y]
   */
  getFlatInteriorPoint () {
    // 缓存限制
    if (this._flatInteriorPointRevision !== this.revision ) {
      /*
      const flatCenter = [this.extent.centerX, this.extent.centerY]

      const ends = [this.getCoordinates()[0].length * 2]
      const orientedFlatCoordinates = this.getOrientedFlatCoordinates()

      this._flatInteriorPoint = linearRings(
        orientedFlatCoordinates, 0, ends, this.stride, flatCenter, 0)

      */
      this._flatInteriorPointRevision = this.revision

      const coords = this.getCoordinates();
      let minY = Infinity;
      let target;
      coords[0].forEach(coord => {
        if (coord[1] < minY) {
          minY = coord[1];
          target = coord;
        }
      })

      if (target) {
        this._flatInteriorPoint = [target[0], target[1]];
      }
    }

    return  this._flatInteriorPoint
  }

  move(x = 0, y = 0, opts) {
    let beyond
    if (opts && opts.beyond) {
      beyond = opts.beyond
    }

    const oldCoordinates = this.getCoordinates();
    let newCoordinates = [];

    oldCoordinates.forEach(coordinates => {
      let newCoords = [];
      coordinates.forEach(coords => {
        newCoords.push([
          coords[0] + x,
          coords[1] + y
        ]);
      });
      newCoordinates.push(newCoords);
    });

    let newBezier = [];

    this.bezier.forEach(item => {
      let newPoint = item.points.map(item => {
        return [
          item[0] + x,
          item[1] + y
        ]
      })
      newBezier.push({
        start: item.start,
        ringIndex: item.ringIndex,
        points: newPoint
      })
    })

    if (!beyond) {
      this.setCoordinates(newCoordinates);
      this.setBezier(newBezier);
      return;
    }

    let isBeyond = newCoordinates.some(coordinates => {
      return coordinates.some(coord => {
        if (
          coord[0] < beyond.xmin ||
          coord[0] > beyond.xmax ||
          coord[1] < beyond.ymin ||
          coord[1] > beyond.ymax
        ) {
          return true;
        }
      })
    });

    if (!isBeyond) {
      this.setCoordinates(newCoordinates);
      this.setBezier(newBezier);
    }
  }

  /**
   * 对多边形的边进行顺序化
   * getOrientedFlatCoordinates
   * @returns {*}
   */
  getOrientedFlatCoordinates () {
    let orientedFlatCoordinates

    const flatCoordinates = []
    const coordinates = this.getCoordinates()[0]

    coordinates.forEach( point => {
      flatCoordinates.push(point[0], point[1])
    })

    const ends = [flatCoordinates.length]

    if (linearRingsAreOriented(flatCoordinates, 0, ends, this.stride)) {
      orientedFlatCoordinates = flatCoordinates
    } else {
      orientedFlatCoordinates = flatCoordinates.slice()
      orientedFlatCoordinates.length = orientLinearRings(orientedFlatCoordinates,
        0, ends, this.stride)
    }

    // this.orientedRevision_ = this.getRevision()

    return orientedFlatCoordinates
  }

  /**
   * 判断当前多边形是否有洞
   *
   * @method hasHoles
   * @returns {boolean}
   */
  hasHoles () {
    return this.getCoordinates().length > 1
  }

  /**
   * 获取多边形坐标
   *
   * @method getCoordinates
   * @returns {[*,*]}
   */
  getCoordinates () {
    return this.rings
  }

  /**
   * 得到最后一个点的坐标，显示表单
   *
   * @param {Number} offsetX x的偏移量
   * @param {Number} offsetY y的偏移量
   * @returns {[*,*]}
   */
  getFormShowPosition (offsetX = 0, offsetY = 0) {
    const coordinates = this.getCoordinates()[0]
    if (coordinates.length === 0) {
      return
    }

    const lastPoint = coordinates[coordinates.length - 2]
    return [lastPoint[0] - offsetX, lastPoint[1] - offsetY]
  }

  /**
   * 设置多边形的坐标
   *
   * @method setCoordinates
   * @param coords
   */
  setCoordinates (coords) {
    this.rings = coords
    this._extent = null
    this.stride = 2
    this.changed()
  }

  /**
   * getCoordinateIndex
   * @param coord
   * @returns {*|number}
   */
  getCoordinateIndex (coord) {
    const coords = this.getCoordinates()
    for (let i = 0, len = coords.length; i < len; i++) {
      const rings = coords[i]
      for (let j = 0,jLen = rings.length; j < jLen; j++) {
        const point = rings[j]
        if (point[0] === coord[0] && point[1] === coord[1]) {
          return {
            ringIndex: i,
            index: j
          }
        }
      }
    }

    return {
      ringIndex: -1,
      index: -1
    }
  }

  /**
   * 克隆多边形
   *
   * @method clone
   * @returns {Polygon}
   */
  clone () {
    const coords = this.getCoordinates()

    const newCoordinates = []
    let outRings = []
    coords.forEach( ring => {
      ring.forEach(points => {
        outRings.push([points[0], points[1]])
      })

      newCoordinates.push(outRings)
      outRings = []
    })

    const newPolygon = new Polygon()
    newPolygon.fixedPointSize = this.fixedPointSize
    newPolygon.setCoordinates(newCoordinates)

    return newPolygon
  }

  /**
   * 获取位于coord坐标点的ring
   *
   * @method getSelectAtCoords
   * @param coord {Array}
   * @returns {Array}
   */
  getHoleAtCoord(coord) {
    const [x, y] = coord
    const coords = []
    for ( let i = 1, l = this.rings.length; i < l; i++ ) {
      let ring = this.rings[i]
      if( this._inOneRing(x, y, ring) ) {
        coords.push(ring)
      }
    }
    return coords
  }

  /**
   * 根据内内环的坐标删除内环
   *
   * @method deleteHoles
   * @params holes {Array}
   * @returns {undefined}
   */
  deleteHoles(holes) {
    let indexs = [];
    let bezier = this.bezier;
    let newBezier = [];
    const findBezierByRingIndex = function(index) {
      return bezier.find(b => {
        return b.ringIndex === index;
      });
    }

    const newRings = this.rings.filter((ring, index) => {
      if ( index === 0 ) {
        const b = findBezierByRingIndex(index);
        newBezier.push(b);
        return true;
      }

      const isDeleted = holes.some(hole => {
        if (this._isRingsEqual(hole, ring)) {
          indexs.push(index);
          return true;
        }
        return false;
        //return this._isRingsEqual(hole, ring);
      });

      if (!isDeleted) {
        const b = findBezierByRingIndex(index);
        newBezier.push(b);
      }

      return !isDeleted;
    })

    this.setCoordinates(newRings);

    newBezier.forEach((b, index) => {
      if (b) {
        b.ringIndex = index;
      }
    });

    newBezier = newBezier.filter(b => b);
    this.setBezier(newBezier);

    return indexs;
  }

  /**
   * 判断两个环的坐标是否相等
   *
   * @method _isRingsEqual
   * @params ring1 {Array}
   * @params ring2 {Array}
   * @returns {Boolean}
   */
  _isRingsEqual(ring1, ring2) {
    return ring1.join(",") === ring2.join(",");
  }

  setBezier(data) {
    this.bezier = data;
    this.changed();
  }

  addBezier(data) {
    this.bezier.push(data);
    this.changed();
  }

  getBezierItem(data) {
    let coords = this.getCoordinates();
    return {
      start: data.start,
      ringIndex: data.ringIndex,
      points: data.points,
      renderPoints: [
        coords[data.ringIndex][data.start],
        ...data.points,
        coords[data.ringIndex][data.start + 1]
      ]
    }
  }

  getBezierVertex(pixelCoordinate, tolerance) {
    let vertex, i;
    let d = this.bezier.find(item => {
      return item.points.some((p, index) => {
        const include = ExtentUtil.containsPoint(
          [
            p[0] - tolerance,
            p[1] - tolerance,
            p[0] + tolerance,
            p[1] + tolerance
          ],
          pixelCoordinate
        )
        if (include) {
          vertex = p;
          i = index;
          return true;
        }
      })
    })
    return {
      vertex,
      data: d,
      index: i
    }
  }

  updateBezierByStartIndex(index, ringIndex, data) {
    this.bezier.some(item => {
      if (item.ringIndex === ringIndex && item.start === index) {
        item.points = data;
        this.changed();
        return true;
      }
    })
  }

  _getAllControls() {
    let r = [];
    this.bezier.forEach(item => {
      r.push(...item.points);
    })
    return r;
  }

  removeBezier(data) {
    this.setBezier(
      this.bezier.filter(item => !(item.ringIndex === data.ringIndex && item.start === data.start))
    )
  }

  hasBezier(ringIndex, index) {
    return this.bezier.find(item => item.ringIndex === ringIndex && item.start === index);
  }

  readCoordinates(coordinates, types) {
    coordinates.forEach(item => {
      let first = item[0];
      let last = item[item.length - 1];
      if (first[0] !== last[0] || first[1] !== last[1]) {
        item.push(first.slice());
      }
    })
    if (!types || !types.length) {
      this.setCoordinates(coordinates);
    } else {
      let coords = [];
      let beziers = [];
      coordinates.forEach((coordinates, ringIndex) => {
        const read = this.readRingCoordinates(coordinates, types[ringIndex], ringIndex);
        coords.push(read.coords);
        beziers = beziers.concat(read.bezier);
      })
      console.log(coords, beziers)
      this.setCoordinates(coords);
      this.setBezier(beziers);
    }

  }

  readRingCoordinates(coordinates, types, ringIndex) {
    let coords = [];
    let bezier = [];
    let LIndex = 0;
    const getNextLIndex = (start, types) => {
      let next;
      for (let i = start, len = types.length; i < len; i++) {
        if (types[i] === 'L') {
          next = i;
          break ;
        }
      }
      return next ? next : -1;
    }

    for (let i = 0, len = coordinates.length; i < len; i++) {
      if (types[i] === 'C') {
        if (types[i - 1] === 'L') {
          let nextIndex = getNextLIndex(i, types);
          bezier.push({
            start: LIndex - 1,
            ringIndex,
            points: coordinates.slice(i, nextIndex)
          })
        } else {}
      } else {
        coords.push(coordinates[i]);
        LIndex++;
      }
    }

    return { coords, bezier }
  }

  exportCoordinates() {
    let coordinates = this.getCoordinates();
    let bezier = this.bezier;
    const startIndex = {};
    bezier.forEach(item => {
      startIndex[`${item.ringIndex}-${item.start}`] = item;
    })
    const coords = [];
    const types = [];
    coordinates.forEach((item, ringIndex) => {
      const ringCoords = [];
      const ringType = [];
      item.forEach((subItem, index) => {
        ringCoords.push(subItem);
        ringType.push('L');
        if (startIndex[`${ringIndex}-${index}`]) {
          startIndex[`${ringIndex}-${index}`].points.forEach(point => {
            ringCoords.push(point);
            ringType.push('C')
          })
        }
      })
      coords.push(ringCoords);
      types.push(ringType.join(''));
    })
    return { coordinates: coords, types: types };
  }

  /**
   * 自动吸附
   * @param {*} x x坐标
   * @param {*} y y坐标
   * @param {*} tolerance 吸附范围
   * @param {*} type 吸附类型 1顶点 2边缘
   */
  intersectPoint(x, y, tolerance = 3, type = 1) {
    // const rings = [...this._rings].pop()
    // 吸附外环
    const rings = [...this._rings][0];

    if(type === 1) {
      const point = rings.find(item => {
        const pointDistance = distance([x,y], item)
        if (pointDistance <= tolerance) {
          return true
        }
      })
      return point;
    }

    // 边缘
    if(type === 2) {
      let i = 0;
      let point = null;
      let min = tolerance;

      while(i < rings.length - 1) {
        // 点到线的距离
        const distance = pointDistanceToSegment([x, y], [rings[i], rings[i + 1]]);
        if(distance <= tolerance && distance <= min) {
          min = distance;
          // 点的投影
          point = getProjectionPointOnLine(rings[i], rings[i + 1], [x, y]);
        }
        i++;
      }
      return point;
    }
  }
}
